<template>
  <div class="fs-swipe" :class="{ 'fs-swipe__hide': !isLoad }" ref="swipeDom">
    <div
      class="fs-swipe__track"
      :style="moveStyle"
      :class="moveTouchClass"
      @touchstart="touchStart"
      @touchmove="touchMove"
      @touchend="touchEnd"
      @touchcancel="touchEnd"
    >
      <slot />
    </div>
  </div>
</template>

<script lang="ts">
import { reactive, ref, watch, defineComponent, getCurrentInstance, computed, onMounted, nextTick } from "vue";
import { useChildren } from "./useRelation";
import type { ComponentInstance, Swipe, SwipeChild, SwipeProvide, SwipeTouch, SwipeTrack } from "./type";
import { getDirection, getRealStyle, preventDefault } from "./utlis";
export const SWIPE_KEY = Symbol("fs-swipe");

export default defineComponent({
  props: {
    stopPropagation: {
      type: Boolean,
      default: true,
    },
    touchable: {
      type: Boolean,
      default: true,
    },
    limit: {
      type: Number,
      default: 0.2,
    },
    unActiveStyle: {
      type: Object,
      default: () => ({}),
    },
  },
  setup(props, { emit, expose }) {
    function linkChild() {
      const { children, linkChildren } = useChildren<ComponentInstance>(SWIPE_KEY);

      linkChildren({
        fsSwipe: (getCurrentInstance() as any).proxy,
      } as SwipeProvide);
      return {
        children,
      };
    }
    const { children } = linkChild();
    const s_children = computed(() => children);
    const swipeDom = ref<HTMLDivElement>();
    const isLoad = ref(false);
    const child = reactive<SwipeChild>({
      list: [],
      width: 0,
      offsetX: 0,
      dom: [],
    });
    const track = reactive<SwipeTrack>({
      offsetX: 0,
      width: 0,
    });
    const swipe = reactive<Swipe>({
      width: 0,
      index: 0,
      syncIndex: 0,
    });
    const touch = reactive<SwipeTouch>({
      startX: 0,
      startY: 0,
      moveX: 0,
      endX: 0,
      isTouch: false,
      direction: "none",
    });

    // 控制scroll移动距离样式
    const moveStyle = computed(() => {
      const { offsetX, width } = track;
      return {
        width: `${width}px`,
        transform: `translate3d(${-offsetX}px, 0, 0)`,
      };
    });
    // 判断是否处于触摸状态来动态改变transition时间
    const moveTouchClass = computed(() => {
      const { isTouch } = touch;
      return {
        "fs-swipe__track-transition": !isTouch,
      };
    });
    // 轮播图长度
    const childLength = computed(() => child.list.length);

    // 初始化轮播图状态
    const initSwipe = () => {
      child.list = s_children.value;
      child.dom = child.list.map(({ $el }: any) => $el);
      initData();
    };

    const initData = () => {
      const activeSwipe: HTMLDivElement = child.dom[swipe.index];
      // 获取到该元素宽、高
      const { width: childWidth } = getRealStyle(activeSwipe);
      // 保存宽长度
      child.width = childWidth;
      // 偏移量初始化
      child.offsetX = (window.innerWidth - childWidth) / 2;
      // 整个轮播图容器宽度：屏幕宽度
      swipe.width = getRealStyle(swipeDom.value as HTMLElement).width;
      // 移动容器的宽度初始设置：孩子个数 * 孩子宽度
      track.width = child.list.length * child.width;
      // 移动容器的初始偏移量：屏幕宽度 - 单张图片宽度 / 2 + 偏移的位置
      track.offsetX = touch.endX = (child.width - swipe.width) / 2 + swipe.index * child.width;
      // 计算初始化完成后控制样式展示
      isLoad.value = true;
    };

    // 设置移动滚动距离
    const moveDis = (x: number) => {
      track.offsetX = touch.moveX = x;
    };

    // 过渡延迟，主要处理无缝衔接的过渡效果
    const onTransitionEnd = (callback: () => void) => {
      setTimeout(() => {
        callback();
      }, 250);
    };
    // 设置偏移量
    const setOffset = (index: number) => {
      const { offsetX, width: itemWidth } = child;
      touch.endX = track.offsetX = index * itemWidth - offsetX;
      if (index >= childLength.value) {
        swipe.syncIndex = 0;
        onTransitionEnd(() => {
          touch.isTouch = true;
          touch.endX = track.offsetX = -offsetX;
          swipe.index = 0;
        });
      } else if (index < 0) {
        swipe.syncIndex = childLength.value - 1;
        onTransitionEnd(() => {
          touch.isTouch = true;
          touch.endX = track.offsetX = (childLength.value - 1) * itemWidth - child.offsetX;
          swipe.index = childLength.value - 1;
        });
      } else {
        swipe.index = swipe.syncIndex = index;
      }
    };

    const touchStart = (e: TouchEvent) => {
      const { clientX, clientY } = e.touches[0];
      touch.isTouch = true;
      touch.startX = clientX;
      touch.startY = clientY;
    };

    const touchMove = (e: TouchEvent) => {
      const { clientX, clientY } = e.touches[0];
      const { startX, startY, endX } = touch;
      const deltaX = clientX - startX;
      const deltaY = clientY - startY;
      const direction = getDirection(Math.abs(deltaX), Math.abs(deltaY));
      if (direction === "horizontal") {
        preventDefault(e, props.stopPropagation);
        moveDis(startX - clientX + endX);
        touch.direction = clientX < startX ? "left" : "right";
      }
    };

    const touchEnd = (e: TouchEvent) => {
      touch.isTouch = false;
      const { pageX: endX } = e.changedTouches[0];
      const { width: itemWidth } = child;
      const { startX } = touch;
      let index = swipe.index;
      if (Math.abs(startX - endX) > itemWidth * props.limit) {
        startX > endX ? index++ : index--;
      }
      setOffset(index);
    };

    watch(
      () => swipe.index,
      (index) => {
        emit("change", index);
      },
      {
        immediate: true,
      }
    );

    watch(
      () => children,
      (value: any) => {
        if (value.length) {
          nextTick(() => {
            initSwipe();
          });
        }
      },
      { deep: true, immediate: true }
    );

    expose({
      initSwipe,
    });

    return {
      touchStart,
      touchMove,
      touchEnd,
      childLength,
      swipeDom,
      moveStyle,
      moveTouchClass,
      isLoad,
      child,
      track,
      swipe,
      touch,
    };
  },
});
</script>

<style scoped lang="scss">
.fs-swipe {
  position: relative;
  width: 100%;
  height: 100%;
  transition: opacity 0.3s;
  &__hide {
    opacity: 0;
  }
  &__track {
    // display: flex;
    height: 100%;
    &-transition {
      transition: transform 300ms;
    }
  }
}
</style>
